﻿using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Filtry.Data;
using Filtry.Utilities;

namespace Filtry.Logic
{
    public static class Bilateral
    {
        public static GrayscaleImage BilateralFilter(this GrayscaleImage img, double deviationS, double deviationR,
                                                     int kernelSize)
        {
            if (kernelSize == 0)
                kernelSize = (int)Math.Ceiling(deviationS * 3) * 2 + 1;
            GrayscaleImage newImg = img.Clone();
            double[] kernelS = Tools.CalculateKernel(deviationS, kernelSize);
            byte[] pixels = PerformFiltering(newImg.GetAllPixels(), kernelS, newImg.Width, newImg.Height, deviationR);

            newImg.SetAllPixels(pixels);
            return newImg;
        }



        private static byte[] PerformFiltering(byte[] pixels, double[] kernelS, int width, int height, double deviationR)
        {
            double[] firstPass = Array.ConvertAll((byte[])pixels.Clone(), input => (double)input);
            var originalPixels = (double[])firstPass.Clone();
            int halfSize = kernelS.Length / 2;

            var a = new Stopwatch(); //debug
            a.Start(); //debug

            Parallel.For(halfSize, width - halfSize, i =>
                                                         {
                                                             for (int j = 0; j < height; j++)
                                                             {
                                                                 firstPass[i + j * width] =
                                                                     CalculatePoint(kernelS, i, j,
                                                                                    originalPixels,
                                                                                    Direction.Horizontal,
                                                                                    width, deviationR);
                                                             }
                                                         });
            var secondPass = (double[])firstPass.Clone();

            Parallel.For(0, width, i =>
                                       {
                                           for (int j = halfSize; j < height - halfSize; j++)
                                           {
                                               secondPass[i + j*width] =
                                                   CalculatePoint(kernelS, i, j,
                                                                  firstPass,
                                                                  Direction.Vertical,
                                                                  width, deviationR);
                                           }
                                       }
                );

            a.Stop(); //debug
            TimeSpan ts = a.Elapsed; //debug
            Debug.WriteLine("{0:00}:{1:00}:{2:00}.{3:00}", //debug
                            ts.Hours, ts.Minutes, ts.Seconds, //debug
                            ts.Milliseconds / 10); //debug


            var result = new byte[secondPass.Length];
            for (int i = 0; i < secondPass.Length; i++)
            {
                result[i] = (byte)Math.Min(255.0, secondPass[i]);
            }

            return result;
        }

        private static double CalculatePoint(double[] kernelS, int i, int j, double[] originalPixels,
                                             Direction direction, int width, double deviationR)
        {
            double result = 0.0;
            int halfSize = kernelS.Length / 2;
            int xCorrection = 0;
            int yCorrection = 0;

            if (direction == Direction.Horizontal)
                xCorrection = -halfSize;
            else
                yCorrection = -halfSize;

            var neighbours = new double[kernelS.Length];

            for (int k = 0; k < kernelS.Length; k++)
            {
                neighbours[k] = originalPixels[(i - xCorrection) + width * (j - yCorrection)];
                if (direction == Direction.Horizontal)
                    xCorrection++;
                else
                    yCorrection++;
            }

            double[] kernelR = Tools.CalculateKernelR(neighbours, deviationR, originalPixels[i + width * j]);

            if (direction == Direction.Horizontal)
                xCorrection = -halfSize;
            else
                yCorrection = -halfSize;


            double sum = 0;
            for (int index = 0; index < kernelR.Length; index++)
            {
                result += originalPixels[(i - xCorrection) + width * (j - yCorrection)] * kernelS[index] * kernelR[index];
                sum += kernelS[index] * kernelR[index];
                if (direction == Direction.Horizontal)
                    xCorrection++;
                else
                    yCorrection++;
            }
            return result / sum;
        }
    }
}